#include "MemoryManager.h"

MemoryManager::MemoryManager(Configuration* conf)
{
	pid = 0;
	m_conf = conf;
	m_numOfEntries = m_conf->PageSize / 4;
	m_numOfKernelPerProcess = m_numOfEntries;//m_conf->NumOfKernelPagesInDisk / m_numOfEntries;
	m_numOfUserPerProcess = m_numOfKernelPerProcess * m_numOfEntries;// m_conf->NumOfUserPagesInDisk / m_numOfEntries;
	m_processes = new PCBS();
	
	int maxKernelProcesses = m_conf->NumOfKernelPagesInDisk / m_numOfKernelPerProcess;
	int maxUserProcesses = m_conf->NumOfUserPagesInDisk / m_numOfUserPerProcess;
	m_numOfProcesses = (maxKernelProcesses < maxUserProcesses) ? maxKernelProcesses : maxUserProcesses;

	m_MM = new Memory(m_conf->PageSize, m_conf->NumOfUserPagesInMM, m_conf->NumOfKernelPagesInMM, false);
	m_Disk = new Memory(m_conf->PageSize, m_conf->NumOfUserPagesInDisk, m_conf->NumOfKernelPagesInDisk, true);
	m_mmu = new MemoryManagementUnit(m_numOfProcesses, m_numOfKernelPerProcess, m_numOfEntries, m_conf->PageSize,m_MM);
	m_PageReplacementManager = new PageReplacementManager(m_conf->PageSize, m_conf->NumOfKernelPagesInMM, m_conf->NumOfUserPagesInMM, m_MM, m_Disk, conf->AlgorithmForUser, conf->AlgorithmForKernel, m_numOfEntries, m_mmu, m_processes, conf->WSClockDelta);
}

MemoryManager::~MemoryManager(void)
{
	delete m_processes;
	delete m_MM;
	delete m_Disk;
	delete m_mmu;
	delete m_PageReplacementManager;
}

int MemoryManager::createProcess()
{
	if(m_processes->size() < m_numOfProcesses)
	{
		PCB* proc = new PCB;
		
		proc->pid = ++pid;
		proc->virtualTime = 0;
		proc->kernelPageNum = m_numOfKernelPerProcess;
		proc->userPageNum = m_numOfUserPerProcess;
		m_mmu->addProcess(proc->pid);
		proc->kernelPageStart = m_Disk->allocate(KERNEL, 0, proc->pid);
		resetPage(proc->kernelPageStart);
		for(int i=1 ; i< m_numOfKernelPerProcess ; i++)
		{
			int kerPage = m_Disk->allocate(KERNEL, i, proc->pid);
			resetPage(kerPage);
		}
		proc->userPageStart = m_Disk->allocate(USER, 0, proc->pid);
		for(int i=1 ; i< m_numOfUserPerProcess ; i++)
		{
			m_Disk->allocate(USER, i, proc->pid);
		}		
		m_processes->insert(PCBS::value_type(proc->pid, proc));
		return proc->pid;
	}
	else return -1;
}

void MemoryManager::resetPage(int pageNum)
{	
	int fault = PAGE_FAULT;
	for(int j=0 ; j<m_numOfEntries ; j++)
	{
		m_Disk->write(KERNEL,pageNum,j*4,(byte*)&fault,4, 0);
		//printf("%d\n", *m_Disk->read(KERNEL,pageNum,j*4));
	}	
}

int MemoryManager::deleteProcess(int pid)
{
	if(m_processes->find(pid) != m_processes->end())
	{
		PCB* proc = (*m_processes)[pid];
		m_processes->erase(pid);
		for(int i=0 ; i<m_numOfKernelPerProcess ; i++)
		{
			if(!m_mmu->freePage(i,pid))
			{
				for(int j=0 ; j<m_numOfEntries; j++)
				{
					char* add = &m_Disk->m_pages[KERNEL][proc->kernelPageStart + i].data[j*4];     //m_Disk->read(KERNEL, proc->kernelPageStart + i ,j*4);
					int pageNum = *((int*)add);

					if(pageNum != PAGE_FAULT)
					{
						m_MM->free(pageNum,USER);
					}
				}
			}
			m_Disk->free(proc->kernelPageStart + i,KERNEL);
		}
		for(int i=0 ; i<m_numOfUserPerProcess ; i++)
		{
			m_Disk->free(proc->userPageStart + i,USER);
		}
		m_mmu->deleteProcess(pid);

		delete proc;

		return 1;
	}
	else{
		return 0;
	}
}

byte* MemoryManager::read(int vAddr, int ammount)
{
	byte* ans = new byte[ammount+1];
	ans[ammount] = 0;
	int page = getPage(vAddr);
	int offset = vAddr % m_conf->PageSize;

	m_currProcess->virtualTime++;

	for(int i=0 ; i<ammount ; i++)
	{
		if(offset == m_conf->PageSize)
		{
			//replace page to next page
			page = getPage(vAddr + i);
			offset = 0;
		}
		ans[i] = *m_MM->read(USER,page,offset, m_currProcess->virtualTime);
		offset++;
	}
	m_currProcess->virtualTime++;
	return ans;
}


void MemoryManager::write(int vAddr,const byte c[])
{
	int page = getPage(vAddr);
	int offset = vAddr % m_conf->PageSize;

	m_currProcess->virtualTime++;

	for(int i=0 ; c[i]!='\0' ; i++)
	{
		if(offset == m_conf->PageSize)
		{
			//replace page to next page
			page = getPage(vAddr + i);
			offset = 0;
		}
		m_MM->write(USER,page,offset,&c[i],1, m_currProcess->virtualTime);
		offset++;
	}
}

int MemoryManager::getPage(int vAddr)
{
	int page = PAGE_FAULT;
	while(page == PAGE_FAULT)
	{
		page = m_mmu->getPage(vAddr, m_currProcess->virtualTime);
		if(page==PAGE_FAULT)
		{
			if(m_mmu->RequestDomain == KERNEL)
			{
				m_PageReplacementManager->loadKernelPage(m_currProcess->pid,m_mmu->RequestedPage);
			}
			else
			{
				int userDiskPage = vAddr/m_conf->PageSize;// + m_currProcess->userPageStart;
				m_PageReplacementManager->loadUserPage(m_currProcess->pid,userDiskPage);
			}
		}
	}	
	return page;	
}

int MemoryManager::contextSwitch(int id)
{
	if(m_processes->find(id) != m_processes->end())
	{
		m_currProcess = (*m_processes)[id];
		m_mmu->contextSwitch(m_currProcess->pid);
		return 1;
	}
	else
	{
		return 0;
	}
}


void MemoryManager::printUserMM()
{
	m_MM->printUser();
}

void MemoryManager::printKernelMM()
{
	m_MM->printKernel();
}

void MemoryManager::printMMUTable()
{
	m_mmu->printTable();
}

double MemoryManager::getUserHitRate()
{
	return m_mmu->getUserHitRate();
}

double MemoryManager::getKernelHitRate()
{
	return m_mmu->getKernelHitRate();
}
